<!DOCTYPE html>


<html lang="en">
  

    <head>
      <meta charset="utf-8" />
        
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>《JavaScript高级程序设计》- 第十四章：DOM |  欢迎</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/favicon.ico" />
       
<link rel="stylesheet" href="/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/atom.xml" title="欢迎" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-JavaScript学习/《JavaScript高级程序设计》- 第十四章：DOM"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  《JavaScript高级程序设计》- 第十四章：DOM
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/2022/02/27/JavaScript%E5%AD%A6%E4%B9%A0/%E3%80%8AJavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E3%80%8B-%20%E7%AC%AC%E5%8D%81%E5%9B%9B%E7%AB%A0%EF%BC%9ADOM/" class="article-date">
  <time datetime="2022-02-27T13:57:43.000Z" itemprop="datePublished">2022-02-27</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/JavaScript%E5%AD%A6%E4%B9%A0/">JavaScript学习</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> Word count:</span>
            <span class="post-count">7.6k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> Reading time≈</span>
            <span class="post-count">30 min</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h2 id="第十四章：DOM"><a href="#第十四章：DOM" class="headerlink" title="第十四章：DOM"></a>第十四章：DOM</h2><p>文档对象模型（Document Object Model）是HTML 和 XML文档的编程接口；就是开发者可以通过JavaScript编程，添加、删除、修改页面的各个部分；</p>
<h3 id="14-1-节点层级"><a href="#14-1-节点层级" class="headerlink" title="14.1 节点层级"></a>14.1 节点层级</h3><p>任何HTML\XML文档都可以表示为由节点构成的层级结构。</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">title</span>&gt;</span>Sample Page<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>Hello World!<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<img src="https://pic.imgdb.cn/item/621a1b862ab3f51d9193b1d1.png" style="zoom:50%;" />

<blockquote>
<p>在HTML中，文档元素（document）每个文档只能拥有<strong>一个</strong>，而且是<strong>最外层元素</strong>，其有且<strong>只有一个直接子元素</strong><code>&lt;html&gt;</code></p>
</blockquote>
<h3 id="14-1-1-Node类型"><a href="#14-1-1-Node类型" class="headerlink" title="14.1.1 Node类型"></a>14.1.1 Node类型</h3><p><strong>DOM Level 1</strong>描述了Node的接口，这个接口是<strong>所有DOM节点类型都必须实现的</strong>；</p>
<p>Node接口在<code>JavaScript</code>中被实现为Node类型。</p>
<blockquote>
<p>在JavaScript中，所有节点类型都继承Node类型，一共有<strong>12</strong>种类型并用12个<strong>常量表示</strong></p>
<p>（也可以使用其中的<strong>数字</strong>代替）：</p>
</blockquote>
<ul>
<li><p>Node.ELEMENT_NODE（1）</p>
</li>
<li><p>Node.ATTRIBUTE_NODE（2）</p>
</li>
<li><p>Node.TEXT_NODE（3）</p>
</li>
<li><p>Node.CDATA_SECTION_NODE（4）</p>
</li>
<li><p>Node.ENTITY_REFERENCE_NODE（5）</p>
</li>
<li><p>Node.ENTITY_NODE（6）</p>
</li>
<li><p>Node.PROCESSING_INSTRUCTION_NODE（7）</p>
</li>
<li><p>Node.COMMENT_NODE（8）</p>
</li>
<li><p>Node.DOCUMENT_NODE（9）</p>
</li>
<li><p>Node.DOCUMENT_TYPE_NODE（10）</p>
</li>
<li><p>Node.DOCUMENT_FRAGMENT_NODE（11）</p>
</li>
<li><p>Node.NOTATION_NODE（12）  </p>
</li>
</ul>
<blockquote>
<p>节点信息</p>
</blockquote>
<ul>
<li>nodeName：存储着节点的名字，如div、img</li>
<li>nodeValue：存储着节点的值，若为标签，则值为<code>null</code></li>
<li>nodeType：存储着节点类型，对应着上面的12种类型</li>
</ul>
<blockquote>
<p>节点关系</p>
</blockquote>
<p>文档中各节点都具有关系，相当于一个<strong>族谱</strong>，主要为父元素和子元素两种区别；</p>
<ol>
<li><code>childNodes</code>：每个节点都拥有该属性，该属性包含一个<code>NodeList</code>实例，是一个<strong>类数组对象</strong>，<strong>有序</strong>存储着该节点的<strong>所有直接子节点</strong>。<code>NodeList</code>实例是<strong>实时的活动对象</strong>，这就意味着，<strong>DOM树改变</strong>、<strong>NodeList也会改变</strong>，反之亦然；<ul>
<li>由于是一个类数组对象，所以可以通过<code>[]</code>访问其中元素；</li>
<li>也可以通过<code>length</code>属性，获取子元素个数。</li>
</ul>
</li>
<li><code>parentNode</code>：指向父节点</li>
<li><code>previousSibling</code>：指向前一个兄弟节点，<code>childNodes[0].previousSibling</code>为<code>null</code></li>
<li><code>nextSibling</code>：指向后一个兄弟节点，<code>childNodes[childNodes.length-1].nextSibling</code>为<code>null</code></li>
<li><code>firstChild/lastChild</code>：指向第一个子节点、最后一个子节点</li>
<li><code>hasChildNodes()</code>：返回true，表示有一个或多个节点；</li>
<li><code>ownerDocument</code>：指向文档中<strong>唯一的</strong>文档节点（document）。</li>
</ol>
<blockquote>
<p>操纵节点</p>
</blockquote>
<p>添加节点：</p>
<ol>
<li><p>appendChild()：<strong>向尾部追加一个节点</strong>；接收一个参数：</p>
<ul>
<li>需要添加的节点</li>
</ul>
</li>
<li><p>insertBefore()：在<strong>参照节点前插入一个节点</strong>；接收两个参数：</p>
<ul>
<li>需要添加的节点；</li>
<li>参照节点；</li>
</ul>
<p>如果参照节点为<code>null</code>，则效果与<code>appendChild()</code>一样；</p>
</li>
<li><p>replaceChild()：<strong>替换目标节点</strong>；接收两个参数：</p>
<ul>
<li>要插入的节点</li>
<li>要替换的节点</li>
</ul>
<p>被替换的节点，会<strong>完全消失</strong>在DOM树中。</p>
</li>
</ol>
<p>删除节点：</p>
<ol>
<li><p>removeChild()：<strong>删除一个节点</strong>；接收一个参数：</p>
<ul>
<li>要删除的节点</li>
</ul>
<p><strong>会返回被删除的节点</strong></p>
</li>
</ol>
<p>克隆节点：</p>
<ol>
<li><p>cloneNode()：克隆一个节点，接收一个参数：</p>
<ul>
<li>布尔值，<strong>表示是否深克隆</strong>；如果为<code>true</code>，则<strong>克隆该节点以及其子节点。</strong></li>
</ul>
<p>返回被克隆的节点；如果被克隆的节点<strong>没有指定父节点</strong>，则称为<strong>孤儿节点</strong>。</p>
<blockquote>
<p>注意，该克隆方法，<strong>不会克隆节点的<code>JavaScript</code>属性</strong>，如时间处理程序，<strong>只复制<code>HTML</code>属性</strong>。</p>
</blockquote>
</li>
</ol>
<h4 id="14-1-2-Document类型"><a href="#14-1-2-Document类型" class="headerlink" title="14.1.2 Document类型"></a>14.1.2 Document类型</h4><p><code>JavaScript</code>中表示文档节点类型。在浏览器中，<code>document</code>是<code>HTMLDocument</code>的实例，<strong>表示整个页面</strong>；</p>
<p>document对象是<strong>只读的</strong>，一般<strong>不会存在</strong>诸如<code>appendChild()</code>等方法</p>
<blockquote>
<p>Document类型有如下特征：</p>
<ul>
<li>nodeType等于9；</li>
<li>nodeName值为”#document”；</li>
<li>nodeValue值为null；</li>
<li>parentNode值为null；</li>
<li>ownerDocument值为null；</li>
<li>子节点可以是DocumentType（最多一个） 、 Element（最多一个） 、 ProcessingInstruction或Comment类型。  </li>
</ul>
</blockquote>
<blockquote>
<p>文档子节点：</p>
</blockquote>
<p>document提供了三个访问子节点的快捷方式：</p>
<ol>
<li>document.documentElement：始终指向<code>&lt;html&gt;</code>元素</li>
<li>document.body：始终指向<code>&lt;body&gt;</code>元素</li>
<li>document.doctype：始终指向<code>&lt;!DOCTYPE&gt;</code>元素</li>
</ol>
<blockquote>
<p>文档信息</p>
</blockquote>
<p>document提供了浏览器加载网页的信息</p>
<ol>
<li>document.title：获取<code>&lt;title&gt;</code>标签的内容，可修改，并会渲染到网页中。</li>
<li>document.URL：获取当前页面的URL</li>
<li>document.domain：获取当前页面的域名</li>
<li>document.referrer：获取当前页面来源</li>
</ol>
<p>注意：</p>
<ul>
<li><p>URL和domain是相关的，若URL为<code>https://www.baidu.com/search=xxxx</code>，则域名为<code>www.baidu.com</code>;</p>
</li>
<li><p>后面三个属性中，只有<code>domain</code>是可设置的。处于安全考虑，<code>domain</code><strong>只能设置为URL中的子集</strong>：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 当前URL为：www.p2p.baidu.com</span></span><br><span class="line"><span class="built_in">document</span>.domain = <span class="string">&#x27;p2p.baidu&#x27;</span>;	<span class="comment">// 成功</span></span><br><span class="line"><span class="built_in">document</span>.domain = <span class="string">&#x27;baidu.com&#x27;</span>;	<span class="comment">// 成功</span></span><br><span class="line"><span class="built_in">document</span>.domain = <span class="string">&#x27;hahaha.net&#x27;</span>;	<span class="comment">// 失败</span></span><br></pre></td></tr></table></figure></li>
<li><p>当页面包含来自不同子域的窗格（<code>&lt;frame&gt;</code>）或内嵌窗格（<code>&lt;iframe&gt;</code>）时，设置<code>domain</code>是有用的。因为<strong>跨源通信存在安全隐患</strong>；所以可以把<strong>每个页面上的<code>domain</code>都设置为相同的值</strong>，就<strong>可以访问对方的JavaScript对象</strong>。</p>
</li>
<li><p><code>domain</code>属性，一旦收紧了，就不能再放松了</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 当前URL为：www.p2p.baidu.com</span></span><br><span class="line"><span class="built_in">document</span>.domain = <span class="string">&#x27;p2p.baidu.com&#x27;</span>;	<span class="comment">// 放松了，成功</span></span><br><span class="line"><span class="built_in">document</span>.domain = <span class="string">&#x27;baidu.com&#x27;</span>;		<span class="comment">// 继续放松了，成功</span></span><br><span class="line"><span class="built_in">document</span>.domain = <span class="string">&#x27;p2p.baidu.com&#x27;</span>;	<span class="comment">// 再次收紧了，错误！</span></span><br></pre></td></tr></table></figure></li>
</ul>
<blockquote>
<p>定位元素</p>
</blockquote>
<p>document提供了定位元素的方法</p>
<ul>
<li>getElementById()：根据id属性获取节点。因为id是唯一的，所以返回第一个找到的。</li>
<li>getElementsByClassName()：根据class属性获取节点。</li>
<li>getElementsByTagName()：根据标签名获取节点。</li>
<li>getElementsByName()：根据name属性获取节点。</li>
</ul>
<p>除了第一个方法，其余的都是返回一个<code>HTMLCollection</code>对象；该对象也是<strong>类数组对象</strong>。</p>
<blockquote>
<p>特殊集合</p>
</blockquote>
<ul>
<li>document.anchors包含文档中所有带name属性的<code>&lt;a&gt;</code>元素；</li>
<li>document.forms包含文档中所有<code>&lt;form&gt;</code>元素；</li>
<li>document.images包含文档中所有<code>&lt;img&gt;</code>元素；</li>
<li>document.links包含文档中所有带href属性的<code>&lt;a&gt;</code>元素。  </li>
</ul>
<blockquote>
<p> 文档写入</p>
</blockquote>
<ul>
<li>write()：向文档输入信息，接收一个参数：输入内容</li>
<li>writeln()：上前者一致，只不过会自动添加换行符</li>
</ul>
<p>注意：</p>
<ul>
<li><p>这两个方法需要只能在页面渲染器使用，如果渲染完毕在调用，内容会覆盖整个页面</p>
</li>
<li><p>当使用这个方法动态写入外部资源时，需要注意：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>document.write() Example<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">&quot;text/javascript&quot;</span>&gt;</span><span class="handlebars"><span class="xml"></span></span></span><br><span class="line"><span class="xml"><span class="handlebars">        document.write(&quot;<span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">\</span>&quot;<span class="attr">text</span>/<span class="attr">javascript</span>\&quot; <span class="attr">src</span>=<span class="string">\</span>&quot;<span class="attr">file.js</span>\&quot;&gt;</span>&quot; +</span></span></span><br><span class="line"><span class="xml"><span class="handlebars">            &quot;&lt;\/script&gt;&quot;);</span></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>在输出内容的第二个script前面，<strong>必需加上转义字符</strong>，不然会匹配错误；</p>
</li>
</ul>
<h4 id="14-1-3-Element类型"><a href="#14-1-3-Element类型" class="headerlink" title="14.1.3 Element类型"></a>14.1.3 Element类型</h4><p>除Document类型外，Element类型就是Web开发中最常用的类型；Elment表示HTML元素，<strong>对外暴露访问元素标签名、子节点和属性的能力</strong>。</p>
<blockquote>
<p>Element类型具有以下特征：</p>
<ul>
<li>nodeType等于1；</li>
<li>nodeName值为元素的标签名；</li>
<li>nodeValue值为null；</li>
<li>parentNode值为Document或Element对象；子节点可以是Element、 Text、 Comment、ProcessingInstruction、 CDATASection类型。  </li>
</ul>
</blockquote>
<blockquote>
<p>特征提取</p>
</blockquote>
<ul>
<li><code>nodeName</code> 或 <code>tagName</code>属性都可以获取<code>Elment</code>元素的标签名，且在<code>HTML</code>中，元素标签名始终以<strong>全大写表示</strong>；</li>
</ul>
<p><em>为了防止提取标签名的时候，大小写混乱，推荐如下操作：</em></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(element.tagName.toLowerCase() == <span class="string">&#x27;div&#x27;</span>)&#123;</span><br><span class="line">    <span class="comment">// do something</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<blockquote>
<p>HTML元素</p>
</blockquote>
<p>所有HTML元素，都是通过<code>HTMLElement</code>类型标识，包括直接实例和间接实例；</p>
<p><code>HTMLElement</code>直接继承自Element，并<strong>增加了一些标准属性</strong>：</p>
<ul>
<li><code>id</code>， 元素在文档中的唯一标识符；</li>
<li><code>title</code>， 包含元素的额外信息， 通常以提示条形式展示；</li>
<li><code>lang</code>， 元素内容的语言代码（很少用） ；</li>
<li><code>dir</code>， 语言的书写方向（”ltr”表示从左到右， “rtl”表示从右到左， 同样很少用） ；</li>
<li><code>className</code>， 相当于class属性， 用于指定元素的CSS类（因为class是ECMAScript关键字， 所以不能直接用这个名字）。</li>
</ul>
<p>可以直接获取、修改这些值：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// &lt;div id=&#x27;myDiv&#x27; class=&#x27;myClass&#x27; title=&#x27;hello world&#x27;&gt;</span></span><br><span class="line"><span class="keyword">let</span> div = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;myDiv&#x27;</span>);</span><br><span class="line"><span class="built_in">console</span>.log(div.class);	<span class="comment">// myClass</span></span><br><span class="line">div.class = <span class="string">&#x27;myLess&#x27;</span>;</span><br><span class="line"><span class="built_in">console</span>.log(div.class);	<span class="comment">// myLess</span></span><br></pre></td></tr></table></figure>

<p>修改这些属性会对页面产生影响。</p>
<blockquote>
<p>操作属性</p>
</blockquote>
<p>每个元素有零个或多个属性，我们可以通过如下三个方法来操作属性：</p>
<ul>
<li>getAttribute()：<strong>获取属性</strong>，接收一个参数：属性名</li>
<li>setAttribute()：<strong>设置属性</strong>，接收两个参数：属性名，属性值</li>
<li>removeAttribute()：<strong>移除属性</strong>，接收一个参数：属性名</li>
</ul>
<p>注意：以上说所三种操作属性的方法：</p>
<ul>
<li>既可以操作标准属性（又称：<strong>公认属性</strong>）、也可以操作自定义属性；</li>
<li>属性名不区分大小写</li>
<li>在HTML5的规范中，，自定义属性需要加<code>data-</code>为前缀，方便验证；</li>
<li>自定义属性不会称为DOM对象属性，即不会称为公认属性或标准属性；</li>
<li>不仅仅可以获取一般属性，还可以获取事件属性：如onclick；</li>
<li>如果使用<code>getAttribute()</code>来获取事件属性值，则以字符串的形式返回源代码；通常直接访问事件属性：<code>element.onclick</code>，则返回一个函数；</li>
<li>开发者一般使用<code>getAttribute()</code>来访问自定义属性，而公认属性可以直接访问：<code>element.id</code></li>
</ul>
<blockquote>
<p>attributes属性</p>
</blockquote>
<p><code>attributes</code>属性与<code>childNodes</code>属性类似，也是类似<code>NodeList</code>的实时集合，每个属性表示为<strong>Attr节点</strong>，保存在<code>NamedNodeMap</code>对象中，其包含如下方法：</p>
<ul>
<li><code>getNamedItem(name)</code>， 返回nodeName属性等于name的节点；</li>
<li><code>removeNamedItem(name)</code>， 删除nodeName属性等于name的节点；</li>
<li><code>setNamedItem(node)</code>， 向列表中添加node节点， 以其nodeName为索引；</li>
<li><code>item(pos)</code>， 返回索引位置pos处的节点  。</li>
</ul>
<p><em>一般开发者都使用操作属性的三个方法，而attributes属性的应用场景主要在需要迭代元素上所有属性的时候</em></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">outputAttributes</span>(<span class="params">element</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> pairs = [];</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">let</span> i=<span class="number">0</span> , len = element.attributes.length ; i &lt; len ; ++i)&#123;</span><br><span class="line">        <span class="keyword">const</span> attribute = element.attributes[i];</span><br><span class="line">        pairs.push(<span class="string">`<span class="subst">$&#123;attribute.nodeName&#125;</span>=<span class="subst">$&#123;attribute.nodeValue&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> pairs.join(<span class="string">&quot; &quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//&lt;div id=&#x27;myDiv&#x27; class=&#x27;myClass&#x27; title=&#x27;hello world&#x27;&gt;</span></span><br><span class="line"><span class="keyword">let</span> div = <span class="built_in">document</span>.getElementById(<span class="string">&#x27;myDiv&#x27;</span>);</span><br><span class="line"><span class="built_in">console</span>.log(outputAttributes(div)); <span class="comment">// id=myDiv class=myClass title=hello world</span></span><br></pre></td></tr></table></figure>



<blockquote>
<p>创建元素</p>
</blockquote>
<p>通过<code>document.createElement(tagName)</code>来创建元素，在HTML中，标签名时<strong>不区分大小写</strong>的；</p>
<p>注意：</p>
<ul>
<li>创建出来的元素可以通过<strong>属性操作</strong>的三个方法，或者<strong>直接操作公认属性</strong>来定义、修改；</li>
<li>创建出来的元素，需要通过如<code>appendChild()</code>等方法，才能插入DOM树中；</li>
<li>当创建出来的元素，被添加到DOM树中，浏览器会立即渲染出来，如果对该元素有所修改，也会立即在浏览器中反映出来。</li>
</ul>
<blockquote>
<p>元素后代</p>
</blockquote>
<p>元素可以拥有任意多个子元素，<code>childNodes</code>属性包含该元素的所有直接子节点；这些子节点可以是其他元素、文本节点、注释或处理指令；</p>
<p><em>注意</em>：<code>&lt;ul&gt;</code>下一共有<strong>7个子节点</strong>，分别为：三个<code>&lt;li&gt;</code>，四个空的Text节点</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">&quot;myList&quot;</span>&gt;</span> 一个空</span><br><span class="line">一个空	<span class="tag">&lt;<span class="name">li</span>&gt;</span>Item 1<span class="tag">&lt;/<span class="name">li</span>&gt;</span> 两个空</span><br><span class="line">两个空	<span class="tag">&lt;<span class="name">li</span>&gt;</span>Item 2<span class="tag">&lt;/<span class="name">li</span>&gt;</span>	三个空</span><br><span class="line">三个空	<span class="tag">&lt;<span class="name">li</span>&gt;</span>Item 3<span class="tag">&lt;/<span class="name">li</span>&gt;</span> 四个空</span><br><span class="line"><span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span><br></pre></td></tr></table></figure>

<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">ul</span> <span class="attr">id</span>=<span class="string">&quot;myList&quot;</span>&gt;</span><span class="tag">&lt;<span class="name">li</span>&gt;</span>Item 1<span class="tag">&lt;/<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">li</span>&gt;</span>Item 2<span class="tag">&lt;/<span class="name">li</span>&gt;</span><span class="tag">&lt;<span class="name">li</span>&gt;</span>Item 3<span class="tag">&lt;/<span class="name">li</span>&gt;</span><span class="tag">&lt;/<span class="name">ul</span>&gt;</span>	<span class="comment">&lt;!--&gt;三个子节点&lt;--!&gt;</span></span><br></pre></td></tr></table></figure>





<h4 id="14-1-4-Text类型"><a href="#14-1-4-Text类型" class="headerlink" title="14.1.4 Text类型"></a>14.1.4 Text类型</h4><p>Text节点，包含纯文本、转移后的HTML字符；其具有以下特征：</p>
<ul>
<li>nodeType等于3；</li>
<li>nodeName值为”#text”；</li>
<li>nodeValue值为节点中包含的文本；</li>
<li>parentNode值为Element对象；</li>
<li>不支持子节点。  </li>
</ul>
<p>文本节点，可以通过<code>nodeValue</code>或<code>data</code>直接访问文本内容；可以通过<code>length</code>属性获取文本字符数量；同时也<strong>暴露了如下方法：</strong></p>
<ul>
<li>appendData(text)， 向节点末尾添加文本text；</li>
<li>deleteData(offset, count)， 从位置offset开始删除count个字符；</li>
<li>insertData(offset, text)， 在位置offset插入text；</li>
<li>replaceData(offset, count, text)， 用text替换从位置offset到offset + count的文本；</li>
<li>splitText(offset)， 在位置offset将当前文本节点拆分为两个文本节点；</li>
<li>substringData(offset, count)， 提取从位置offset到offset + count的文本。  </li>
</ul>
<p><em>注意</em>：</p>
<ul>
<li><p>若节点在DOM树中，如果被修改，就会立即反映在浏览器中；</p>
</li>
<li><p>HTML代码会被转化为<strong>实体编码</strong>，即：<strong>小于号、大于号、引号会被转义：</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 输出为&quot;Some &amp;lt;strong&amp;gt;other&amp;lt;/strong&amp;gt; message&quot;</span></span><br><span class="line">div.firstChild.nodeValue = <span class="string">&quot;Some &lt;strong&gt;other&lt;/strong&gt; message&quot;</span>;</span><br></pre></td></tr></table></figure></li>
</ul>
<blockquote>
<p>创建文本节点</p>
</blockquote>
<p>通过<code>document.createTextNode()</code>来实现；文本创建后，其<code>ownerDocument</code>属性会被设置为<code>document</code></p>
<blockquote>
<p>规范化文本节点</p>
</blockquote>
<p>一个文本节点就足以表达一个文本字符串了，如果元素中，拥有两个文本节点，显然是不恰当的；</p>
<p>我们可以在父节点上，调用<code>normalize()</code>来进行合并：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> element = <span class="built_in">document</span>.createElement(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> text1 = <span class="built_in">document</span>.createTextNode(<span class="string">&#x27;Hello,&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> text2 = <span class="built_in">document</span>.createTextNode(<span class="string">&#x27;world!&#x27;</span>);</span><br><span class="line">element.appendChild(text1);</span><br><span class="line">element.appendChild(text2);</span><br><span class="line"><span class="built_in">console</span>.log(element.length);	<span class="comment">// 2</span></span><br><span class="line">element.normalize();</span><br><span class="line"><span class="built_in">console</span>.log(element.length);	<span class="comment">// 1</span></span><br></pre></td></tr></table></figure>



<blockquote>
<p>拆分文本节点</p>
</blockquote>
<p>显然，有合并就会有拆分，<code>splitText()</code>就是用来实现拆分文本节点的；</p>
<p>其接收一个参数：开始拆分的位置。</p>
<h4 id="14-1-5-Comment类型"><a href="#14-1-5-Comment类型" class="headerlink" title="14.1.5 Comment类型"></a>14.1.5 Comment类型</h4><p>DOM的注释，就是Comment类型；其具有以下特征：</p>
<ul>
<li>nodeType等于8；</li>
<li>nodeName值为”#comment”；</li>
<li>nodeValue值为注释的内容；</li>
<li>parentNode值为Document或Element对象；</li>
<li>不支持子节点。  </li>
</ul>
<p><em>与Text类似，但是Comment不常用，这里就不多做解释了</em></p>
<h4 id="14-1-6-DocumentType类型"><a href="#14-1-6-DocumentType类型" class="headerlink" title="14.1.6 DocumentType类型"></a>14.1.6 DocumentType类型</h4><p>DocumentType包含文档的信息，具有如下特征：</p>
<ul>
<li>nodeType等于10；</li>
<li>nodeName值为文档类型的名称；</li>
<li>nodeValue值为null；</li>
<li>parentNode值为Document对象；</li>
<li>不支持子节点。  </li>
</ul>
<p>DocumentType节点，一般只有name属性有用，其包含文档类型的名称；</p>
<h4 id="14-1-7-DocumentFragment类型"><a href="#14-1-7-DocumentFragment类型" class="headerlink" title="14.1.7 DocumentFragment类型"></a>14.1.7 DocumentFragment类型</h4><p>在所有节点类型中，DocumentFragment类型是<strong>唯一一个在标记中没有对于表示的类型</strong>。DOM将文档片段定义为<code>轻量级</code>文档，能够包含和操作节点，却没有完整文档的额外消耗；其具有以下特征：</p>
<ul>
<li>nodeType等于10；</li>
<li>nodeName值为文档类型的名称；</li>
<li>nodeValue值为null；</li>
<li>parentNode值为Document对象；</li>
<li>不支持子节点。  </li>
</ul>
<blockquote>
<p>文档片段的用处</p>
</blockquote>
<p><em>文档片段充当其他被添加到文档树的节点的仓库。</em></p>
<p>因为<strong>每向DOM树添加一个节点</strong>，DOM树就会<strong>重新渲染一次</strong>，如果有<strong>多个添加操作</strong>，就会<strong>多次重新渲染</strong>，降低性能、效率；</p>
<p>我们可以将需要添加的节点，<strong>先加到文档片段中</strong>，然后<strong>再将文档片段添加到DOM树上</strong>，这样只需要<strong>重新渲染一次</strong>就可以，而且<strong>文档片段本身不会渲染到DOM树上</strong>；</p>
<blockquote>
<p>我们可以使用<code>createDocumentFragment()</code>来创建文档片段；</p>
</blockquote>
<h4 id="14-1-8-Attr类型"><a href="#14-1-8-Attr类型" class="headerlink" title="14.1.8 Attr类型"></a>14.1.8 Attr类型</h4><p><code>attributes</code>属性中，都是Attr类型的节点，具有如下特征：</p>
<ul>
<li>nodeType等于2；</li>
<li>nodeName值为属性名；</li>
<li>nodeValue值为属性值；</li>
<li>parentNode值为null；</li>
<li>在HTML中不支持子节点；</li>
<li>在XML中子节点可以是Text或EntityReference。  </li>
</ul>
<p>尽管Attr节点也是DOM树的一种节点，但是它很少被使用，因为开发者喜欢使用<code>getAttribute</code>等方法；</p>
<h3 id="14-2-DOM编程"><a href="#14-2-DOM编程" class="headerlink" title="14.2 DOM编程"></a>14.2 DOM编程</h3><p>操作DOM，即可以在HTML中完成，也可以通过JavaScript来完成；</p>
<h4 id="14-2-1-动态脚本"><a href="#14-2-1-动态脚本" class="headerlink" title="14.2.1 动态脚本"></a>14.2.1 动态脚本</h4><p>通过向网页插入<code>&lt;script&gt;</code>元素，从而插入脚本。</p>
<h4 id="14-2-2-动态样式"><a href="#14-2-2-动态样式" class="headerlink" title="14.2.2 动态样式"></a>14.2.2 动态样式</h4><p>与<code>14.2.1</code>相似，通过向网页插入<code>&lt;style&gt;</code>元素，从而插入样式。</p>
<blockquote>
<p>不管是动态加载脚本，还是动态加载样式；都是一个异步的过程。</p>
</blockquote>
<h4 id="14-2-3-操作表格"><a href="#14-2-3-操作表格" class="headerlink" title="14.2.3 操作表格"></a>14.2.3 操作表格</h4><p>表格是HTML中，是一个比较复杂的结构，</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">table</span> <span class="attr">border</span>=<span class="string">&quot;1&quot;</span> <span class="attr">width</span>=<span class="string">&quot;100%&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">tbody</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span>&gt;</span>Cell 1,1<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span>&gt;</span>Cell 2,1<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">tr</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span>&gt;</span>Cell 1,2<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">td</span>&gt;</span>Cell 2,2<span class="tag">&lt;/<span class="name">td</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">tr</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">tbody</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">table</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>若通过DOM来创建表格，可以说是比较麻烦的；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建表格</span></span><br><span class="line"><span class="keyword">let</span> table = <span class="built_in">document</span>.createElement(<span class="string">&quot;table&quot;</span>);</span><br><span class="line">table.border = <span class="number">1</span>;</span><br><span class="line">table.width = <span class="string">&quot;100%&quot;</span>;</span><br><span class="line"><span class="comment">// 创建表体</span></span><br><span class="line"><span class="keyword">let</span> tbody = <span class="built_in">document</span>.createElement(<span class="string">&quot;tbody&quot;</span>);</span><br><span class="line">table.appendChild(tbody);</span><br><span class="line"><span class="comment">// 创建第一行</span></span><br><span class="line"><span class="keyword">let</span> row1 = <span class="built_in">document</span>.createElement(<span class="string">&quot;tr&quot;</span>);</span><br><span class="line">tbody.appendChild(row1);</span><br><span class="line"><span class="keyword">let</span> cell1_1 = <span class="built_in">document</span>.createElement(<span class="string">&quot;td&quot;</span>);</span><br><span class="line">cell1_1.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 1,1&quot;</span>));</span><br><span class="line">row1.appendChild(cell1_1);</span><br><span class="line"><span class="keyword">let</span> cell2_1 = <span class="built_in">document</span>.createElement(<span class="string">&quot;td&quot;</span>);</span><br><span class="line">cell2_1.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 2,1&quot;</span>));</span><br><span class="line">row1.appendChild(cell2_1);</span><br><span class="line"><span class="comment">// 创建第二行</span></span><br><span class="line"><span class="keyword">let</span> row2 = <span class="built_in">document</span>.createElement(<span class="string">&quot;tr&quot;</span>);</span><br><span class="line">tbody.appendChild(row2);</span><br><span class="line"><span class="keyword">let</span> cell1_2 = <span class="built_in">document</span>.createElement(<span class="string">&quot;td&quot;</span>);</span><br><span class="line">cell1_2.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 1,2&quot;</span>));</span><br><span class="line">row2.appendChild(cell1_2);</span><br><span class="line"><span class="keyword">let</span> cell2_2= <span class="built_in">document</span>.createElement(<span class="string">&quot;td&quot;</span>);</span><br><span class="line">cell2_2.appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 2,2&quot;</span>));</span><br><span class="line">row2.appendChild(cell2_2);</span><br><span class="line"><span class="comment">// 把表格添加到文档主体</span></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(table);</span><br></pre></td></tr></table></figure>

<p><strong>但是DOM给<code>&lt;table&gt;</code>、<code>&lt;tbody&gt;</code>、<code>&lt;tr&gt;</code>元素添加了一些属性和方法，提高了创建的效率：</strong></p>
<blockquote>
<p><code>&lt;table&gt;</code>元素添加了以下属性和方法：</p>
</blockquote>
<ul>
<li>  caption， 指向<code>&lt;caption&gt;</code>元素的指针（如果存在） ；</li>
<li>tBodies， 包含<code>&lt;tbody&gt;</code>元素的HTMLCollection；</li>
<li> tFoot， 指向<code>&lt;tfoot&gt;</code>元素（如果存在） ；</li>
<li> tHead， 指向<code>&lt;thead&gt;</code>元素（如果存在） ；</li>
<li>rows， 包含表示所有行的HTMLCollection；</li>
<li> createTHead()， 创建<code>&lt;thead&gt;</code>元素， 放到表格中， 返回引用；createTFoot()， 创建<code>&lt;tfoot&gt;</code>元素，放到表格中， 返回引用；</li>
<li>createCaption()， 创建<code>&lt;caption&gt;</code>元素， 放到表格中， 返回引用；</li>
<li>deleteTHead()， 删除<code>&lt;thead&gt;</code>元素；</li>
<li>deleteTFoot()， 删除<code>&lt;tfoot&gt;</code>元素；</li>
<li>deleteCaption()， 删除<code>&lt;caption&gt;</code>元素；</li>
<li>deleteRow(pos)， 删除给定位置的行；</li>
<li>insertRow(pos)， 在行集合中给定位置插入一行。</li>
</ul>
<blockquote>
<p><code>&lt;tbody&gt;</code>元素添加了以下属性和方法：</p>
</blockquote>
<ul>
<li>  rows， 包含<code>&lt;tbody&gt;</code>元素中所有行的HTMLCollection；</li>
<li>deleteRow(pos)， 删除给定位置的行；</li>
<li>insertRow(pos)， 在行集合中给定位置插入一行， 返回该行的引用。</li>
</ul>
<blockquote>
<p><code>&lt;tr&gt;</code>元素添加了以下属性和方法：</p>
</blockquote>
<ul>
<li>cells， 包含<code>&lt;tr&gt;</code>元素所有表元的HTMLCollection；</li>
<li>deleteCell(pos)， 删除给定位置的表元；</li>
<li>insertCell(pos)， 在表元集合给定位置插入一个表元， 返回该表元的引用。</li>
</ul>
<p><em>通过方法、属性改写后：</em></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建表格</span></span><br><span class="line"><span class="keyword">let</span> table = <span class="built_in">document</span>.createElement(<span class="string">&quot;table&quot;</span>);</span><br><span class="line">table.border = <span class="number">1</span>;</span><br><span class="line">table.width = <span class="string">&quot;100%&quot;</span>;</span><br><span class="line"><span class="comment">// 创建表体</span></span><br><span class="line"><span class="keyword">let</span> tbody = <span class="built_in">document</span>.createElement(<span class="string">&quot;tbody&quot;</span>);</span><br><span class="line">table.appendChild(tbody);</span><br><span class="line"><span class="comment">// 创建第一行</span></span><br><span class="line">tbody.insertRow(<span class="number">0</span>);</span><br><span class="line">tbody.rows[<span class="number">0</span>].insertCell(<span class="number">0</span>);</span><br><span class="line">tbody.rows[<span class="number">0</span>].cells[<span class="number">0</span>].appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 1,1&quot;</span>));</span><br><span class="line">tbody.rows[<span class="number">0</span>].insertCell(<span class="number">1</span>);</span><br><span class="line">tbody.rows[<span class="number">0</span>].cells[<span class="number">1</span>].appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 2,1&quot;</span>));</span><br><span class="line"><span class="comment">// 创建第二行</span></span><br><span class="line">tbody.insertRow(<span class="number">1</span>);</span><br><span class="line">tbody.rows[<span class="number">1</span>].insertCell(<span class="number">0</span>);</span><br><span class="line">tbody.rows[<span class="number">1</span>].cells[<span class="number">0</span>].appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 1,2&quot;</span>));</span><br><span class="line">tbody.rows[<span class="number">1</span>].insertCell(<span class="number">1</span>);</span><br><span class="line">tbody.rows[<span class="number">1</span>].cells[<span class="number">1</span>].appendChild(<span class="built_in">document</span>.createTextNode(<span class="string">&quot;Cell 2,2&quot;</span>));</span><br><span class="line"><span class="comment">// 把表格添加到文档主体</span></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(table);</span><br></pre></td></tr></table></figure>



<h4 id="14-2-4-使用NodeList"><a href="#14-2-4-使用NodeList" class="headerlink" title="14.2.4 使用NodeList"></a>14.2.4 使用NodeList</h4><p>NodeList对象和相关的NameNodeMap、HTMLCollection，都是<strong>“实时的”</strong>集合！这就意味着：<strong>文档结构的变化</strong>会<strong>实时地</strong>在它们身上<strong>反映出来</strong>；</p>
<p><em>一定不要这样做：</em> <strong>因为这样会无限循环下去！</strong></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> divs = <span class="built_in">document</span>.getElementsByTagNmae(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span> ; i &lt; divs.length ; i++ ) &#123;</span><br><span class="line">    <span class="keyword">let</span> div = <span class="built_in">document</span>.createElement(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line">    <span class="built_in">document</span>.body.appendChild(div);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>因为每个查询都会搜索整个文档，最好限制NodeList的操作次数，或者可以选择把NodeList缓存起来使用；</p>
<h3 id="14-3-MutationObserver接口"><a href="#14-3-MutationObserver接口" class="headerlink" title="14.3 MutationObserver接口"></a>14.3 MutationObserver接口</h3><p>MutationObserver接口，可以<strong>在DOM被修改时</strong>，<strong>异步执行回调函数</strong>；</p>
<p>使得MutationObserver可以<strong>观察整个文件、DOM树的一部分、一个元素、甚至一个属性、子节点、文本</strong>等；</p>
<h4 id="14-3-1-基本用法"><a href="#14-3-1-基本用法" class="headerlink" title="14.3.1 基本用法"></a>14.3.1 基本用法</h4><ol>
<li>创建实例：</li>
</ol>
<p>MutationObserver实例需要通过构造函数来创建，并需要传入一个回调执行函数；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&#x27;something changed!&#x27;</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>



<p>新创建的MutationObserver实例并不会关联DOM对象，需要通过<code>observe()</code>方法进行关联；</p>
<p>其接收<strong>两个必需</strong>的参数</p>
<ul>
<li>要观察的DOM对象</li>
<li>MutationObserverInit对象</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">&#x27;attribute was changed!&#x27;</span>);</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">observer.observe(<span class="built_in">document</span>.body, &#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125; );	<span class="comment">// 意为观察body的属性变化</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.className = <span class="string">&#x27;foo&#x27;</span>;</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">&#x27;Changed body class&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Changed body class</span></span><br><span class="line"><span class="comment">// attribute was changed！</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>以上的例子中，<strong>body的后代或者其他非属性修改</strong>，<strong>不会触发回调</strong>；</p>
<p>而且，回调并<strong>非</strong>与实际的DOM变化<strong>同步</strong>的！</p>
</blockquote>
<ol start="2">
<li>回调函数：</li>
</ol>
<p>每个回调都会收到一个<code>MutationRecord</code>实例的数组；该实例数组包含了：</p>
<ul>
<li>发生了什么变化</li>
<li>DOM的拿一些部分收到了影响</li>
</ul>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords);</span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line"><span class="comment">/* 这里是一个mutationRecords实例的一个元素</span></span><br><span class="line"><span class="comment">addedNodes: NodeList []</span></span><br><span class="line"><span class="comment">attributeName: &quot;foo&quot;</span></span><br><span class="line"><span class="comment">attributeNamespace: null</span></span><br><span class="line"><span class="comment">nextSibling: null</span></span><br><span class="line"><span class="comment">oldValue: null</span></span><br><span class="line"><span class="comment">previousSibling: null</span></span><br><span class="line"><span class="comment">removedNodes: NodeList []</span></span><br><span class="line"><span class="comment">target: body</span></span><br><span class="line"><span class="comment">type: &quot;attributes&quot;</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>



<p><em>在回调执行之前，<strong>可能会发生多个满足观察条件的事件</strong>，所以MutationRecord实例的数组，会按照<strong>入队顺序排列</strong>。</em></p>
<blockquote>
<p>MutationRecords实例的属性如下：</p>
</blockquote>
<table>
<thead>
<tr>
<th>属性</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>target</td>
<td>被修改影响的目标节点</td>
</tr>
<tr>
<td>type</td>
<td>字符串，  表示变化的类     型： “attributes”、 “characterData”或”childList”</td>
</tr>
<tr>
<td>oldValue</td>
<td>如果在MutationObserverInit对象中启用（attributeOldValue     或characterData  OldValue为true） ， “attributes”或”characterData”的变化     事件会设置这个属性为被替代的值     “childList”类型的变化始终将这个属性设置为null</td>
</tr>
<tr>
<td>attributeName</td>
<td>对于”attributes”类型的变化，  这里保存被修改属性的名字     其他变化事件会将这个属性设置为null</td>
</tr>
<tr>
<td>attributeNamespace</td>
<td>对于使用了命名空间的”attributes”类型的变化，  这里保存被修     改属性的名字     其他变化事件会将这个属性设置为null</td>
</tr>
<tr>
<td>addedNodes</td>
<td>对于”childList”类型的变化，  返回包含变化中添加节点的NodeList     默认为空NodeList</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td>removedNodes</td>
<td>对于”childList”类型的变化，  返回包含变化中删除节点的NodeList     默认为空NodeList</td>
</tr>
<tr>
<td>previousSibling</td>
<td>对于”childList”类型的变化，  返回变化节点的前一个同胞Node     默认为null</td>
</tr>
<tr>
<td>nextSibling</td>
<td>对于”childList”类型的变化，  返回变化节点的后一个同胞Node     默认为null</td>
</tr>
</tbody></table>
<p>回调还会接收到第二个参数：观察变化的MutationObserver的实例：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords);</span><br><span class="line">    <span class="built_in">console</span>.log(mutationObserver);		<span class="comment">// MutationObserver &#123;&#125;</span></span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br></pre></td></tr></table></figure>



<ol start="3">
<li>取消观察</li>
</ol>
<p>在默认情况下，只要被观察的对象<strong>不被垃圾回收</strong>，MutationObserver的回调<strong>就会响应DOM变化事件</strong>；</p>
<p>如果需要<strong>提前终止执行</strong>，可以使用<code>disconnect()</code>方法；该方法<strong>不仅会停止观察</strong>，而且<strong>也会抛弃已经加入任务队列要异步执行的回调</strong>；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords);</span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line">observer.disconnect();	<span class="comment">// 断开连接后，控制台没有任何输出；</span></span><br></pre></td></tr></table></figure>

<p><em>如果<strong>需要停止观察</strong>，<strong>又不想抛弃已经进入任务队列的回调</strong>；可以这样操作：</em></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords);</span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line"><span class="built_in">setTimeout</span>(<span class="function">()=&gt;</span>&#123;</span><br><span class="line">    observer.disconnect();</span><br><span class="line">&#125;,<span class="number">0</span>);	<span class="comment">// 断开链接，但是控制台有输出</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p>上述代码，将<strong>本该同步执行</strong>的<code>disconnect()</code><strong>变成了异步执行</strong>的代码，也加入到任务队列中，<strong>需要等待</strong>队列中排在<strong>前面的回调执行后</strong>，再执行<code>disconnect()</code></p>
</blockquote>
<ol start="4">
<li>复用MutationObserver实例</li>
</ol>
<p>多次调用<code>observe()</code>方法，可以复用同一个MutationObserver实例，<strong>观察多个不同的目标节点</strong>；</p>
<p>此时，MutationRecord的<code>target</code>属性，可以<strong>标识</strong>发生变化事件的<strong>目标节点</strong>；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords.map( <span class="function">(<span class="params">item</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> item.target</span><br><span class="line">    &#125;));</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> childA = <span class="built_in">document</span>.createElement(<span class="string">&#x27;div&#x27;</span>);</span><br><span class="line"><span class="keyword">let</span> childB = <span class="built_in">document</span>.createElement(<span class="string">&#x27;span&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(childA);</span><br><span class="line"><span class="built_in">document</span>.body.appendChild(childB);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 复用observer</span></span><br><span class="line">observer.observe(childA , &#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line">observer.observe(childB , &#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line">childA.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line">childB.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line"><span class="comment">// 输出</span></span><br><span class="line"><span class="comment">// [div, span]</span></span><br></pre></td></tr></table></figure>



<ol start="5">
<li>重用MutationObserver实例</li>
</ol>
<p>调用<code>disconnect()</code>并<strong>不会结束</strong>MutationObserver的<strong>生命</strong>，<strong>可以重新使用这个观察者</strong>。</p>
<h4 id="14-3-2-MutationObserveInit的观察范围"><a href="#14-3-2-MutationObserveInit的观察范围" class="headerlink" title="14.3.2 MutationObserveInit的观察范围"></a>14.3.2 MutationObserveInit的观察范围</h4><p>MutationObserveInit对象，通过<strong>键对值</strong>的方式，<strong>控制观察的范围</strong>。有如下属性：</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>subtree</td>
<td>布尔值，  表示除了目标节点， 是否观察目标节点的子树（后代）     如果是false， 则只观察目标节点的变化； 如果是true， 则观察目标     节点及其整个子树     默认为false</td>
</tr>
<tr>
<td>attributes</td>
<td>布尔值，  表示是否观察目标节点的属性变化     默认为false</td>
</tr>
<tr>
<td>attributeFilter</td>
<td>字符串数组，  表示要观察哪些属性的变化     把这个值设置为true也会将attributes的值转换为true     默认为观察所有属性</td>
</tr>
<tr>
<td>attributeOldValue</td>
<td>布尔值，  表示MutationRecord是否记录变化之前的属性值     把这个值设置为true也会将attributes的值转换为true     默认为false</td>
</tr>
<tr>
<td>characterData</td>
<td>布尔值，  表示修改字符数据是否触发变化事件     默认为false</td>
</tr>
<tr>
<td>characterDataOldValue</td>
<td>布尔值，  表示MutationRecord是否记录变化之前的字符数据     把这个值设置为true也会将characterData的值转换为true     默认为false</td>
</tr>
<tr>
<td>childList</td>
<td>布尔值，  表示修改目标节点的子节点是否触发变化事件     默认为false</td>
</tr>
</tbody></table>
<p><em>注意：在调用<code>observe()</code>的时候，MutationObserveInit对象中必需有值，不然会因为没有任何变化事件可触发回调，导致报错！</em></p>
<ol>
<li><p>观察属性</p>
<p>通过设置<code>attribute:true</code>就可以观察属性的增、删、改：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords);</span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributes</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);</span><br></pre></td></tr></table></figure>

<p>如果只想观察某些值，可以通过<code>attributeFilter</code>属性来设置白名单；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords);</span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributeFilter</span>:[<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;title&#x27;</span>] &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;foo&#x27;</span>,<span class="string">&#x27;bar&#x27;</span>);	<span class="comment">// 会触发回调</span></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;title&#x27;</span>,<span class="string">&#x27;this is a body&#x27;</span>);	<span class="comment">// 会触发回调</span></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;class&#x27;</span>,<span class="string">&#x27;bodyClass&#x27;</span>);	<span class="comment">// 不会触发回调</span></span><br></pre></td></tr></table></figure>

<p>以上的操作，都是<strong>无记忆</strong>的，即不知道改变之前的上一个值是什么；</p>
<p>如果想获取旧值，可以通过设置<code>attributeOldValue:true</code>来实现。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver( <span class="function">(<span class="params">mutationRecords,mutationObserver</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(mutationRecords.map( <span class="function">(<span class="params">item</span>) =&gt;</span>&#123;</span><br><span class="line">        <span class="keyword">return</span> item.oldValue;</span><br><span class="line">    &#125;));</span><br><span class="line">&#125;)</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body,&#123; <span class="attr">attributeOldValue</span>:<span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;title&#x27;</span>,<span class="string">&#x27;hello&#x27;</span>);	</span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;title&#x27;</span>,<span class="string">&#x27;world&#x27;</span>);</span><br><span class="line"><span class="built_in">document</span>.body.setAttribute(<span class="string">&#x27;title&#x27;</span>,<span class="string">&#x27;!&#x27;</span>);	</span><br><span class="line"></span><br><span class="line"><span class="comment">// 控制台输出：[null, &#x27;hello&#x27;, &#x27;world&#x27;]</span></span><br></pre></td></tr></table></figure></li>
<li><p>观察字符数据</p>
<p>通过设置<code>characterDate:true</code>可以观察文本节点</p>
<p>与观察属性差不多，也可以通过设置，获取旧值；</p>
</li>
<li><p>观察子节点</p>
<p>通过设置<code>childList:true</code>实现；</p>
<p><em>需要注意的是：对子节点进行重新排序，会触发两次事件变化，一次是移除、另一次添加；</em></p>
</li>
<li><p>观察子树</p>
<p>通过设置<code>subtree:true</code>实现；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 清空主体</span></span><br><span class="line"><span class="built_in">document</span>.body.innerHTML = <span class="string">&#x27;&#x27;</span>;<span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver(</span><br><span class="line"><span class="function">(<span class="params">mutationRecords</span>) =&gt;</span> <span class="built_in">console</span>.log(mutationRecords));</span><br><span class="line"><span class="comment">// 创建一个后代</span></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(<span class="built_in">document</span>.createElement(<span class="string">&#x27;div&#x27;</span>));</span><br><span class="line"><span class="comment">// 观察&lt;body&gt;元素及其子树</span></span><br><span class="line">observer.observe(<span class="built_in">document</span>.body, &#123; <span class="attr">attributes</span>: <span class="literal">true</span>, <span class="attr">subtree</span>: <span class="literal">true</span> &#125;);</span><br><span class="line"><span class="comment">// 修改&lt;body&gt;元素的子树</span></span><br><span class="line"><span class="built_in">document</span>.body.firstChild.setAttribute(<span class="string">&#x27;foo&#x27;</span>, <span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line"><span class="comment">// 记录了子树变化的事件</span></span><br><span class="line"><span class="comment">// [</span></span><br><span class="line"><span class="comment">// &#123;</span></span><br><span class="line"><span class="comment">// addedNodes: NodeList[],</span></span><br><span class="line"><span class="comment">// attributeName: &quot;foo&quot;,</span></span><br><span class="line"><span class="comment">// attributeNamespace: null,</span></span><br><span class="line"><span class="comment">// oldValue: null,</span></span><br><span class="line"><span class="comment">// nextSibling: null,</span></span><br><span class="line"><span class="comment">// previousSibling: null,</span></span><br><span class="line"><span class="comment">// removedNodes: NodeList[],</span></span><br><span class="line"><span class="comment">// target: div,</span></span><br><span class="line"><span class="comment">// type: &quot;attributes&quot;,</span></span><br><span class="line"><span class="comment">// &#125;</span></span><br><span class="line"><span class="comment">// ]</span></span><br></pre></td></tr></table></figure>

<p><em>有意思的是：被观察子树的节点，移除子树后，仍能触发变化事件</em></p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 清空主体</span></span><br><span class="line"><span class="built_in">document</span>.body.innerHTML = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver(</span><br><span class="line">	<span class="function">(<span class="params">mutationRecords</span>) =&gt;</span> <span class="built_in">console</span>.log(mutationRecords)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">let</span> subtreeRoot = <span class="built_in">document</span>.createElement(<span class="string">&#x27;div&#x27;</span>),</span><br><span class="line">subtreeLeaf = <span class="built_in">document</span>.createElement(<span class="string">&#x27;span&#x27;</span>);</span><br><span class="line"><span class="comment">// 创建包含两层的子树</span></span><br><span class="line"><span class="built_in">document</span>.body.appendChild(subtreeRoot);</span><br><span class="line">subtreeRoot.appendChild(subtreeLeaf);</span><br><span class="line"><span class="comment">// 观察子树</span></span><br><span class="line">observer.observe(subtreeRoot, &#123; <span class="attr">attributes</span>: <span class="literal">true</span>, <span class="attr">subtree</span>: <span class="literal">true</span> &#125;);</span><br><span class="line"><span class="comment">// 把节点转移到其他子树</span></span><br><span class="line"><span class="built_in">document</span>.body.insertBefore(subtreeLeaf, subtreeRoot);</span><br><span class="line">subtreeLeaf.setAttribute(<span class="string">&#x27;foo&#x27;</span>, <span class="string">&#x27;bar&#x27;</span>);</span><br><span class="line"><span class="comment">// 移出的节点仍然触发变化事件</span></span><br><span class="line"><span class="comment">// [MutationRecord]</span></span><br></pre></td></tr></table></figure></li>
</ol>
<h4 id="14-3-3-异步回调与记录队列"><a href="#14-3-3-异步回调与记录队列" class="headerlink" title="14.3.3 异步回调与记录队列"></a>14.3.3 异步回调与记录队列</h4><p>MutationObserver接口是出以<strong>性能考虑</strong>而设计的，其<strong>核心</strong>是<strong>异步回调</strong>和<strong>记录队列模型</strong>。</p>
<p>为了在大量变化事件发生时，不影响性能。每次变化的信息（<em>经过MutationObserverInit对象设置的信息）</em>都会保存在MutationRecord实例中，然后<strong>添加到记录队列</strong>中。</p>
<p>这个队列对每个MutationRecord实例都是<strong>唯一的</strong>，而且是DOM变化事件的<strong>有序列表</strong>。</p>
<ol>
<li><p>记录队列</p>
<p>每次MutationRecord被添加到MutationObserver的记录队列时， 仅当之前<strong>没有已排期的微任务</strong>回调时<em>（队列中微任务长度为0）</em> ， <strong>才会将观察者注册的回调</strong>（在初始化MutationObserver时传入） 作为微任务<strong>调度到任务队列上</strong>。 这样可以<strong>保证记录队列的内容不会被回调处理两次</strong>。  </p>
<blockquote>
<p>不过在回调的微任务异步执行期间，可能会新触发更多的变化事件。因此，被调用的回调会接收到一个MutationRecord实例数组，顺序为它们进入巨鹿队列的顺序。回调需要扶着处理这个实例数组，因为函数退出之后，这些实例就不存在了。</p>
</blockquote>
</li>
<li><p><code>takeRecords()</code>方法</p>
<p>调用MutationObserver实例的<code>takeRecords()</code>方法，可以<strong>清空记录队列</strong>，<strong>取出并返回</strong>其中<strong>所有</strong>MutationRecord实例。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> observer = <span class="keyword">new</span> MutationObserver(</span><br><span class="line">	<span class="function">(<span class="params">mutationRecords</span>) =&gt;</span> <span class="built_in">console</span>.log(mutationRecords)</span><br><span class="line">);</span><br><span class="line">observer.observe(<span class="built_in">document</span>.body, &#123; <span class="attr">attributes</span>: <span class="literal">true</span> &#125;);</span><br><span class="line"><span class="built_in">document</span>.body.className = <span class="string">&#x27;foo&#x27;</span>;</span><br><span class="line"><span class="built_in">document</span>.body.className = <span class="string">&#x27;bar&#x27;</span>;</span><br><span class="line">2<span class="built_in">document</span>.body.className = <span class="string">&#x27;baz&#x27;</span>;</span><br><span class="line"><span class="built_in">console</span>.log(observer.takeRecords());</span><br><span class="line"><span class="built_in">console</span>.log(observer.takeRecords());</span><br><span class="line"><span class="comment">// [MutationRecord, MutationRecord, MutationRecord]</span></span><br><span class="line"><span class="comment">// []</span></span><br></pre></td></tr></table></figure>

<blockquote>
<p><em>在希望停止观察（<code>disconnect()</code>）</em>的同时，又需要处理被<code>disconnect()</code>抛弃的记录队列中的MutationRecord实例的时候，特别有用。</p>
</blockquote>
</li>
</ol>
<h4 id="14-3-4-性能、内存与垃圾回收"><a href="#14-3-4-性能、内存与垃圾回收" class="headerlink" title="14.3.4 性能、内存与垃圾回收"></a>14.3.4 性能、内存与垃圾回收</h4><p>将<strong>变化回调</strong>委托给微任务来执行可以<strong>保证事件同步触发</strong>， 同时避免随之而来的混乱。 为MutationObserver而<strong>实现的记录队列</strong>， 可以<strong>保证即使变化事件被爆发式地触发</strong>， 也<strong>不会显著地拖慢浏览器</strong>。  </p>
<blockquote>
<p>使用MutationObserver也时有代价的，所以需要注意以下几点：</p>
<ol>
<li><p><strong>MutationObserver实例</strong>与<strong>目标节点</strong>之间的<strong>引用关系是非对称的</strong>。 MutationObserver拥有对要观察的目标节点的弱引用。 因为是弱引用， 所以不会妨碍垃圾回收程序回收目标节点。</p>
<p>然而， 目标节点却拥有对MutationObserver的强引用。 如果目标节点从DOM中被移除， 随后被垃圾回收， 则关的MutationObserver也会被垃圾回收  </p>
</li>
<li><p>记录队列中的每个MutationRecord实例至少包含对已有DOM节点的一个引用。 <strong>如果变化是childList类型， 则会包含多个节点的引用</strong>。 记录队列和回调处理的<strong>默认行为是耗尽这个队列</strong>， 处理每个MutationRecord， 然后让它们超出作用域并被垃圾回收。</p>
<p>有时候可能需要保存某个观察者的完整变化记录。 <strong>保存这些MutationRecord实例</strong>， <strong>也就会保存它们引用的节点</strong>， 因而会<strong>妨碍这些节点被回收</strong>。 </p>
<p>如果<strong>需要尽快地释放内存</strong>， 建议从每个MutationRecord中抽<strong>取出最有用的信息</strong>， 然后<strong>保存到一个新对象中</strong>， 最后抛弃MutationRecord。  </p>
</li>
</ol>
</blockquote>
 
      <!-- reward -->
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>Copyright： </strong>
          
          Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2022/02/27/JavaScript%E5%AD%A6%E4%B9%A0/%E3%80%8AJavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E3%80%8B-%20%E7%AC%AC%E5%8D%81%E5%9B%9B%E7%AB%A0%EF%BC%9ADOM/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/" rel="tag">JavaScript</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2022/02/28/JavaScript%E5%AD%A6%E4%B9%A0/%E3%80%8AJavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E3%80%8B-%20%E7%AC%AC%E5%8D%81%E4%BA%94%E7%AB%A0%EF%BC%9ADOM%E6%8B%93%E5%B1%95/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            《JavaScript高级程序设计》- 第十五章：DOM拓展
          
        </div>
      </a>
    
    
      <a href="/2022/02/26/JavaScript%E5%AD%A6%E4%B9%A0/%E3%80%8AJavaScript%E9%AB%98%E7%BA%A7%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E3%80%8B-%20%E7%AC%AC%E5%8D%81%E4%BA%8C%E7%AB%A0%EF%BC%9ABOM/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">《JavaScript高级程序设计》- 第十二章：BOM</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "jeb2pvxjdeJQOj5OG3AUavGv-gzGzoHsz",
    app_key: "6YlB9tQJTstHMM8GOwFu4uyY",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2022
        <i class="ri-heart-fill heart_icon"></i> 子与子乘
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>Visitors:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>Views:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="欢迎"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/friends">友链</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i></p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-3.6.0.min.js"></script>
 
<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->

<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>